#--
# Copyright (c) 2007 Maximilian Ott
# 
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
# 
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
# 
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
#++
# == Schema Information
# Schema version: 13
#
# Table name: filter_elements
#
#  id       :integer(11)   not null, primary key
#  ftype    :integer(11)   default(0)
#  op       :string(255)   default("and")
#  text_val :string(255)   
#  int_val  :integer(11)   
#  bool_val :boolean(1)    
#  from     :date          
#  to       :date          
#  position :integer(11)   default(0)
#

#
# This class represents one element in a filter chain which can be 
# applied to a record list. It most likely will
# be part of a Report class which holds a chain to determine what
# publications should be included in the report.
#
# We are going to break the clean model-view-helper paradigm
# by including some of the helper functionality in this model.
#
class FilterElement < ActiveRecord::Base
  
  # display_name, field_name, data_type, opts, proc
  FILTERS = []
  
  def self.reset_filter_definitions
    FILTERS.clear
    FILTERS << ['...', nil, :none]
  end
  self.reset_filter_definitions
   
  def self.default_table(def_table)
    @@def_table = def_table
  end
  
  def self.filter(fname, ftype, opts = {}, &block)
    hname = opts[:human] || fname.to_s
    
    if block.nil?
      block = Proc.new do |f, e|
        val = e.val_for_type(ftype)
        #@e = e; @v = val; @f = f; @t = ftype; breakpoint
        if val
          table = opts[:table] || @@def_table
          #tname = "t#{e.position}"
          tname = table
          #col = opts[:col] || "#{fname}_id"
          col = opts[:col] || fname
          
          #f[:from] << "#{table} as #{tname}"
          f[:from] << table
          f[:conditions] << e.op if f[:conditions].size > 0
          
          case ftype
          when :text, :string
            #f[:conditions] << "#{tname}.#{col} = '#{val}'"
            f[:conditions] << "#{tname}.#{col} LIKE '%#{sanitize_sql(val)}%'"
          when :integer
            f[:conditions] << "#{tname}.#{col} = #{val}"
          when :time_span
            from, to = val
            both = (from != nil && to != nil)
            f[:conditions] << "(" if both
            f[:conditions] << "#{tname}.#{col} >= '#{from}'" unless from.nil?
            f[:conditions] << "AND" if both
            f[:conditions] << "#{tname}.#{col} <= '#{to}'" unless to.nil?
            f[:conditions] << ")" if both
          else
            raise "Unimplemented filter for type '#{ftype}'"
          end
        end
      end
    end
    
    FILTERS << [hname, fname, ftype, opts, block]
  end
  
  
  #  attr_accessor :selected, :position, :bool_op, :value, :project_id, :from_date, :to_date
  
  def self.parse(params)
    
    res = []
    position = 1
    (1 .. params.length).each do |i|
      p = params[i.to_s]
      raise 'Wrongly formatted filter definition' if p.nil?
      
      if ((type = p['ftype']) != p.delete('arg_type'))
        # Will be triggered if we change the 'ftype' but
        # still get the value from a previous setting
        p.delete('text_val'); p.delete('bool_val');  p.delete('int_val'); 
#        @p = p; breakpoint
      end
      # remove placeholder values
      p.delete('text_val') if p['text_val'] == '-1'
      p.delete('int_val') if p['int_val'] == '-1'
      
      type = type.to_i
      unless type == 0  # ignore placeholder
        p['position'] = position
        #puts "FILTER_ELEMET>> #{p.inspect}"
        res << self.new(p)
        position += 1
      end
    end
    res << self.new('position' => position) # add placeholder
    res
  end
  
  # Return the filter types in a form usable by 'options_for_select'
  # Should really go into helpers.
  def self.types(fe)
    i = -1
    first = fe.position == 1
    t = FILTERS.collect do |n, t|
      i += 1
      if i == 0
        n = fe.ftype == 0 ? '...' : '... Remove'
      else        
        n = "#{@op} #{n}" unless first
      end
      [n, i]
    end
  end
  
  def val_for_type(ftype)
    case ftype
    when :string, :text
      return self.text_val
    when :integer
      return self.int_val
    when :boolean
      return self.bool_val
    when :time_span
      res = [self.from, self.to]
      return res[0].nil? && res[1].nil? ? nil : res
    when :none
      return nil
    else
      raise "Unimplemented type '#{ftype}'"
    end
  end
  
  def value()
    val_for_type(argument_type)
  end
  
  # Return the name of this filter
  #
  def field_name
    return  FILTERS[self.ftype][1] || ''  
  end
  
  # return the type of the additional argument
  # needed for this filter element.
  # TODO: Find a better description of this
  #
  def argument_type
    return  FILTERS[self.ftype][2] || :none
  end
  
  def ftype=(type)
    @attributes['ftype'] = type
    if argument_type == :time_span
      today = Date.today
      self.from= self.from || Date.civil(today.year, today.month)
      self.to = self.to || Date.civil(today.year, today.month, -1) # -1 .. last day
    end
  end
  
  # Return the SQL query this element imposes in
  # the form of a hash table containing the normal
  # parameters to a find call.
  #
  # If an optional hash is supplied the local
  # constraints are added and that hash is returned
  #
  def to_sql(params = {:from => [], :conditions => []})
    fname = self.field_name
    return params if fname.to_s.empty?
    
    block = FILTERS[self.ftype][4]
    raise "Missing instructions on how to form SQL for 'FilterElement:#{fname}'" if block.nil?
    block.call(params, self)
    logger.debug "FILTER (#{FILTERS[self.ftype].inspect})>>>> " + params.inspect
    params
  end
end


